/*
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is ST_EXT-S_UIMAST.
 *
 * The Initial Developer of the Original Code is
 * University of Rome, Tor Vergata
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 */

/**
 * Constructs a <code>RequestProxyError</code>. This kind of object is used by
 * the proxy framework to throw exceptions.
 * @param message the exception message
 */
function RequestProxyError(message)
{
	this.name = "RequestProxyError";
	this.message = message;
}

// Overrides the <code>Object.toString</code> property to
// have nicer conversion to string
RequestProxyError.prototype.toString = function(){
	return this.name + ': "' + this.message + '"';
}
 
/**
 * Creates a <code>RequestProxy</code> which eases performing Semantic Turkey requests.
 * @param signature a function-like signature which defines the request to do. The last
 *                  formal parameter may be variable length and this is defined in the
 *                  signature appending "..." to the type name. Variable length arguments
 *                  may be instantiates either with a flatten sequence of arguments or
 *                  with an array of values.
 * @return the request proxy
 * @throws RequestProxyError exceptions may be thrown for various reasons, but no exception is
 *                           thrown if the server have provided an XML response.For this reason, 
 *                           itshould be easy to separate your business logic from the exception handlers
 */
 
function createRequestProxy( signature )
{
	// Outer reg exp to identify <service name>, <request name> and <argument list>
	var regExp = /^\s*(?:(POST|GET)\s+)?\s*([a-z]\w*):([a-z]\w*)\s*\(([^\)]*)\)\s*$/i;

	// Termporary variable to store reg exp matches
	var result;
		
	if( result = regExp.exec(signature) ) {
		var method = "GET";				// method (default: GET)
		if(result[1])
			method = result[1].toUpperCase();
		var serviceName = result[2];	// service name
		var requestName = result[3];	// request name
		var parameterList = result[4];
				
		if( parameterList == "" ) {	// if it is a nullary request
			parameterList = {};
		} else {					// otherwise
		
			// separates each formal parameter
			parameterList = parameterList.split(",");	
			
			var o = [];
			
			regExp = /^\s*([a-z]\w*)\s*(\.\.\.)?\s+([a-z]\w*)\s*$/i
			
			for( var i = 0 ; i < parameterList.length ; ++i ) {
				// identify parameter type and name
				if( result = regExp.exec(parameterList[i]) ) {
					
					var converter = null;
					
					switch(result[1])	// selects convertes upon type
					{
						case "String":	// string converter (never fails)
							converter = String;
							break;
						case "Integer":
							converter = function(parameter) { var result = parseInt(parameter); if(result === NaN) throw new RequestProxyError("Bad argument"); return result;}
							break;
						default:
							throw new RequestProxyError("Bad parameter type");
					}
					
					// if it is vararg, then it must be the last argument
					if(result[2] && i + 1 != parameterList.length)
						throw new RequestProxyError("A variable length argument isn't at the end of the parameter list");	
					
					// adds a parameter (it is an array such that 0 = converter 1 = name)
					o[i] = [converter, result[3]];
					
					// Manages the variable length argument
					if(result[2]) {
						o[i].vararg = true;
					} else {
						o[i].vararg = false;
					}
									
				} else {
					throw new RequestProxyError("Signature syntax error");				
				}
			}	
			
			parameterList = o;
			o = null;
		}
		
		result = null;
		regExp = null;
				
		// Creates the proxy
		var fnct = function(){
			/*
			 * Uses outer local variables:
			 * serviceName
			 * requestName
			 * parameterList <-- each property has the name of a parameter and its value is a function
			 *                   able to do type checking and conversion (throws an exception otherwise)
			 */
			 
			if(arguments.length < parameterList.length)
				throw new RequestProxyError("Too few arguments");
			 
			var requestURI = "http://127.0.0.1:1979/semantic_turkey/resources/stserver/STServer?service="+serviceName+"&request="+requestName;
			 
			var i = 0;	// index over formal parameters
			var j = 0;	// index over actual arguments
			
			var p;
			var name;
			var value;
			var data = "";
			
			for(i = 0 ; i < parameterList.length ; ++i) {
				p = parameterList[i];
					
				if(p.vararg) {	// if it is a variable length parameter
				    name = p[1];
				    value = "";
				    
				    // Accepts either flatten arguments or an array
				    if( arguments[j] instanceof Array ) {
				   		for(var l = 0 ; l < arguments[j].length ; ++l)
				   		{
				   			value += "," + encodeURIComponent(p[0](arguments[j][l]));
				   		}
				   		
				   		++j;				   
				    } else {
						for( ; j < arguments.length ; ++j )
						{
							value += "," + encodeURIComponent(p[0](arguments[j]));
						}
					}
					
					value = value.substring(1);
					
					data += "&" + encodeURIComponent(name) + "=" + value;
					
					break;	// but we know that at this point there shouldn't be any parameter
				} else {
					name = p[1];
					value = p[0](arguments[j]);
				
					data += "&" + encodeURIComponent(name) + "=" + encodeURIComponent(value);
					++j;
				}
			}
			
			if(j<arguments.length)
				throw new RequestProxyError("Too many arguments");
			 
			 // Performs the actual call
			 // Gets an instance of XMLHttpRequest
			 var req = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
                                  .createInstance(Components.interfaces.nsIXMLHttpRequest);
               
              if(method == "GET") {   	// method: GET                
	             // Opens the conncetions
	             req.open("GET",requestURI+data,arguments.callee.async);	// choose sync/async
              } else {					// method: POST
 	             // Opens the conncetions
	             req.open("POST",requestURI,arguments.callee.async);	// choose sync/async             
              }
                         
             // Every configuration afert "open" but before "send"
             // Forces text/xml mime-type
             req.overrideMimeType('text/xml');
             
             // Closes the connection after the request
             req.setRequestHeader("connection","close");
             
             // By-passes cache
             req.channel.loadFlags |= Components.interfaces.nsIRequest.LOAD_BYPASS_CACHE;
             
             // Calback
             var callback = arguments.callee.callback;
             
             // Sets the callback
             req.onreadystatechange = function(event){
             	if(callback)
             		callback.call(req,event);
             };
             
             if(method == "GET") {   	// method: GET  
	             // Sends the request
	             req.send(null);
	         } else {					// method: POST
	         	// Sends the request
	         	
				// Convert the data text into an input stream.
				var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
				                          .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
				converter.charset = "UTF-8";
				var stream = converter.convertToInputStream(data.substring(1));	// skips the first &>
	         
	    		req.setRequestHeader("content-type","application/x-www-form-urlencoded");
	    
	         	req.send(stream);
	         }
	                                                
             // If call is synchronous, returns the result
             if( !arguments.callee.async ) {
             	if(req.status == 200) {
             		var r = req.responseXML;
             		if( r != null )
             			return r;
             		else
             			throw new RequestProxyError("Invalid response format");
             	} else {
             		throw new RequestProxyError("HTTP error: " + req.status + " " + req.statusText);
             	}
             }
		};
	
		// Configure the proxy
		fnct.async = false;			// calls are synchronous by default
		fnct.callback = null;		// the callback which will applied to the XMLHttpRequest object
		// Returns the proxy
		return fnct;
	} else {
		throw new RequestProxyError("Signature syntax error");
	}
}
